/*!
 *
 * This program is free software; you can redistribute it and/or modify it under the
 * terms of the GNU General Public License, version 2 as published by the Free Software
 * Foundation.
 *
 * You should have received a copy of the GNU General Public License along with this
 * program; if not, you can obtain a copy at http://www.gnu.org/licenses/gpl-2.0.html
 * or from the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 *
 * Copyright (c) 2002-2018 Hitachi Vantara. All rights reserved.
 *
 */

package org.pentaho.platform.security.policy.rolebased;

import org.pentaho.platform.api.engine.IAuthorizationAction;
import org.pentaho.platform.api.engine.security.userroledao.NotFoundException;
import org.pentaho.platform.api.mt.ITenant;
import org.pentaho.platform.api.mt.ITenantedPrincipleNameResolver;
import org.pentaho.platform.engine.core.system.TenantUtils;
import org.pentaho.platform.repository2.unified.jcr.JcrTenantUtils;
import org.springframework.extensions.jcr.JcrCallback;
import org.springframework.extensions.jcr.JcrTemplate;
import org.springframework.util.Assert;

import javax.jcr.RepositoryException;
import javax.jcr.Session;
import java.io.IOException;
import java.util.*;

/**
 * An {@link IRoleAuthorizationPolicyRoleBindingDao} implementation that uses JCR. Storage is done using nodes and
 * properties, not XML. Storage looks like this:
 * 
 * <pre>
 * {@code 
 * - acme
 *   - .authz
 *     - roleBased
 *       - runtimeRoles
 *         - runtimeRole1
 *           - logicalRole1,logicalRole2 (multi-valued property)
 *         - runtimeRole2
 *           - logicalRole2 (multi-valued property)
 * }
 * </pre>
 * 
 * <p>
 * Note: All multi-valued properties are ordered.
 * </p>
 * 
 * <p>
 * Note: This code runs as the repository superuser. Ideally this would run as the tenant admin but such a named
 * user doesn't exist for us to run as. Now that the repo uses IAuthorizationPolicy for access control, this code
 * MUST continue to run as the repository superuser. This is one reason not to implement this on top of PUR.
 * </p>
 * 
 * @author mlowery
 */
public class JcrRoleAuthorizationPolicyRoleBindingDao extends AbstractJcrBackedRoleBindingDao {

  // ~ Static fields/initializers
  // ======================================================================================

  // ~ Instance fields
  // =================================================================================================

  private JcrTemplate jcrTemplate;

  // ~ Constructors
  // ====================================================================================================

  public JcrRoleAuthorizationPolicyRoleBindingDao( final JcrTemplate jcrTemplate, final Map<String, List<IAuthorizationAction>> immutableRoleBindings,
      final Map<String, List<String>> bootstrapRoleBindings, final String superAdminRoleName,
      final ITenantedPrincipleNameResolver tenantedRoleNameUtils, final List<IAuthorizationAction> authorizationActions ) {
    super(immutableRoleBindings, bootstrapRoleBindings, superAdminRoleName, tenantedRoleNameUtils,
        authorizationActions );
    Assert.notNull( jcrTemplate );
    this.jcrTemplate = jcrTemplate;
  }

  // ~ Methods
  // =========================================================================================================

  /**
   * {@inheritDoc}
   */
  @Override
  public RoleBindingStruct getRoleBindingStruct( final String locale ) {
    return (RoleBindingStruct) jcrTemplate.execute( new JcrCallback() {
      @Override
      public Object doInJcr( final Session session ) throws RepositoryException, IOException {
        return getRoleBindingStruct( session, null, locale );
      }
    } );
  }

  @Override
  public RoleBindingStruct getRoleBindingStruct( final ITenant tenant, final String locale ) {
    if ( ( tenant != null ) && !TenantUtils.isAccessibleTenant( tenant ) ) {
      return new RoleBindingStruct( new HashMap<String, String>(), new HashMap<String, List<String>>(), new HashSet<String>() );
    }
    return (RoleBindingStruct) jcrTemplate.execute( new JcrCallback() {
      @Override
      public Object doInJcr( final Session session ) throws RepositoryException, IOException {
        return getRoleBindingStruct( session, tenant, locale );
      }
    } );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void setRoleBindings( final String runtimeRoleName, final List<String> logicalRoleNames ) {
    setRoleBindings( (ITenant) null, runtimeRoleName, logicalRoleNames );
  }

  @Override
  public void setRoleBindings( final ITenant tenant, final String runtimeRoleName,
                               final List<String> logicalRoleNames ) {
    ITenant tempTenant = tenant;
    if ( tenant == null ) {
      tempTenant = JcrTenantUtils.getTenant( runtimeRoleName, false );
    }
    if ( !TenantUtils.isAccessibleTenant( tempTenant ) ) {
      throw new NotFoundException( "Tenant " + tenant.getId() + " not found" );
    }
    Assert.notNull( logicalRoleNames );
    jcrTemplate.execute( new JcrCallback() {
      @Override
      public Object doInJcr( final Session session ) throws RepositoryException, IOException {
        setRoleBindings( session, tenant, runtimeRoleName, logicalRoleNames );
        return null;
      }
    } );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  @SuppressWarnings( "unchecked" )
  public List<String> getBoundLogicalRoleNames( final List<String> runtimeRoleNames ) {
    // what runtimeRoleNames are in the cache; we don't need to fetch them
    return (List<String>) jcrTemplate.execute( new JcrCallback() {
      @Override
      public Object doInJcr( final Session session ) throws RepositoryException, IOException {
        return getBoundLogicalRoleNames( session, runtimeRoleNames );
      }
    } );
  }

  @Override
  public List<String> getBoundLogicalRoleNames( final ITenant tenant, final List<String> runtimeRoleNames ) {
    if ( ( tenant != null ) && !TenantUtils.isAccessibleTenant( tenant ) ) {
      return new ArrayList<String>();
    }
    // what runtimeRoleNames are in the cache; we don't need to fetch them
    return (List<String>) jcrTemplate.execute( new JcrCallback() {
      @Override
      public Object doInJcr( final Session session ) throws RepositoryException, IOException {
        return getBoundLogicalRoleNames( session, tenant, runtimeRoleNames );
      }
    } );
  }
}
